package ad_exercise_3.controller;

import ad_exercise_3.model.Card;
import ad_exercise_3.model.Card.CardColour;
import ad_exercise_3.model.Card.CardType;
import ad_exercise_3.model.ComputerPlayer;
import ad_exercise_3.model.CustomList;
import ad_exercise_3.model.CustomStack;
import ad_exercise_3.model.HumanPlayer;
import ad_exercise_3.model.Player;

/**
 * 
 * 
 * @author Karen Berntsen (s0815799)
 * @author Erik Stens (s0615374)
 */
public class Game {
	private CustomList<Player>	playerList;
	private CustomStack<Card>	cardStock;
	private CustomStack<Card>	playStack;
	
	private int					currentPlayerIndex;
	private boolean				isPlaying;
	private boolean				invertedDirection;
	private int					takeCardsBeforeTurn;
	
	public Game() {
		initCardStacks();
		initPlayers();
		
		start();
	}
	
	public Player getCurrentPlayer() {
		return playerList.get(currentPlayerIndex);
	}
	
	private void start() {
		System.out.println("A new game has started with " + getNumPlayers() + " players");
		System.out.println();
		
		isPlaying = true;
		Player currentPlayer;
		
		while (isPlaying) {
			currentPlayer = getCurrentPlayer();
			System.out.println("It's " + currentPlayer.getName() + "'s turn");
			// System.out.println("Currently there are " + playStack.size() + " cards on the playStack");
			
			if (takeCardsBeforeTurn > 0) {
				currentPlayer.takeCards(takeCardsBeforeTurn);
				System.out.println("Player " + currentPlayer.getName() + " took " + takeCardsBeforeTurn + " cards from the stack");
				takeCardsBeforeTurn = 0;
			}
			
			Card card = currentPlayer.doTurn();
			
			if (card != null) {
				playStack.add(card);
				System.out.println("Player " + currentPlayer.getName() + " played " + card);
				
				if (getCurrentPlayer().getNumCards() == 0) {
					isPlaying = false;
					showWinMessage();
					break;
				}
				
				doSpecialCases(card);
			}
			
			nextPlayer();
			System.out.println(); // empty line between turns
		}
	}
	
	private void showWinMessage() {
		System.out.println(getCurrentPlayer().getName() + " has won the game!");
	}
	
	private void nextPlayer() {
		currentPlayerIndex += invertedDirection ? playerList.size() - 1 : 1;
		currentPlayerIndex %= playerList.size();
	}
	
	private void initCardStacks() {
		cardStock = new CustomStack<Card>();
		playStack = new CustomStack<Card>();
		
		CardColour colours[] = new CardColour[] { CardColour.RED, CardColour.YELLOW, CardColour.GREEN, CardColour.BLUE };
		for (CardColour cardColour : colours) {
			for (int i = 0; i < 2; i++) { // 1-9 en de actiekaarten komen 2 keer voor per kleur
				for (int value = 1; value < 10; value++) {
					cardStock.addSomewhere(new Card(cardColour, value));
				}
				
				cardStock.addSomewhere(new Card(cardColour, CardType.TAKE_2));
				cardStock.addSomewhere(new Card(cardColour, CardType.INVERT_DIRECTION));
				cardStock.addSomewhere(new Card(cardColour, CardType.SKIP_TURN));
			}
			
			cardStock.addSomewhere(new Card(cardColour, 0));
			cardStock.addSomewhere(new Card(CardType.CHOICE));
			cardStock.addSomewhere(new Card(CardType.CHOICE_TAKE_4));
		}
		
		System.out.println("Shuffled card stack");
	}
	
	private void initPlayers() {
		Player newPlayer;
		playerList = new CustomList<Player>();
		
		newPlayer = new HumanPlayer("Carbon Unit", takeCards(7), this);
		playerList.add(newPlayer);
		System.out.println("A new " + newPlayer.getType() + " named '" + newPlayer.getName() + "' was added to the game");
		
		newPlayer = new ComputerPlayer("HAL-9000", takeCards(7), this);
		playerList.add(newPlayer);
		System.out.println("A new " + newPlayer.getType() + " named '" + newPlayer.getName() + "' was added to the game");
		
		newPlayer = new ComputerPlayer("R2-D2", takeCards(7), this);
		playerList.add(newPlayer);
		System.out.println("A new " + newPlayer.getType() + " named '" + newPlayer.getName() + "' was added to the game");
	}
	
	@SuppressWarnings("unused")
	private void initPlayers(int numPlayers) {
		playerList = new CustomList<Player>();
		
		for (int i = 0; i < numPlayers; i++) {
			playerList.add(new HumanPlayer(takeCards(7), this));
		}
	}
	
	public CustomList<Card> takeCards(int numCards) throws RuntimeException {
		CustomList<Card> cards = new CustomList<Card>();
		if (numCards < cardStock.size()) {
			for (int i = 0; i < numCards; i++) {
				cards.add(cardStock.takeFirst());
			}
		} else {
			int remainingCards = numCards - cardStock.size();
			cards.addAll(cardStock);
			cardStock.clear();
			shuffleCards();
			for (int i = 0; i < remainingCards; i++) {
				cards.add(cardStock.takeFirst());
			}
		}
		
		return cards;
	}
	
	private void shuffleCards() {
		System.out.println("Stack is empty, shuffling all but last card from play stack");
		while (playStack.size() > 1) {
			Card card = playStack.takeFirst();
			if (card.getCardType() == CardType.CHOICE || card.getCardType() == CardType.CHOICE_TAKE_4) {
				card.setCardColour(CardColour.UNKNOWN);
			}
			cardStock.addSomewhere(card);
		}
	}
	
	public int getNumPlayers() {
		return playerList.size();
	}
	
	public boolean cardFits(Card card) {
		if (playStack.isEmpty()) {
			return true;
		}
		
		return card.cardFits(getCurrentCard());
	}
	
	public Card getCurrentCard() {
		return playStack.getLast();
	}
	
	public boolean emptyPlayStack() {
		return playStack.isEmpty();
	}
	
	private void doSpecialCases(Card card) {
		switch (card.getCardType()) {
			case CHOICE_TAKE_4:
				takeCardsBeforeTurn += 4;
			case CHOICE:
				card.setCardColour(getCurrentPlayer().askColour());
				System.out.println("Colour of the choice card is now " + card.getCardColour());
				break;
			case TAKE_2:
				takeCardsBeforeTurn += 2;
				break;
			case INVERT_DIRECTION:
				invertDirection();
				System.out.println("Direction changed to " + (invertedDirection ? " anti-clockwise" : "clockwise"));
				break;
			case SKIP_TURN:
				nextPlayer();
				System.out.println("Player " + getCurrentPlayer().getName() + " has to skip a turn");
				break;
			case NORMAL:
			default:
		}
	}
	
	private void invertDirection() {
		invertedDirection = invertedDirection ? false : true;
	}
	
}
